(function($) {  
    //TODO 需要重构sortable
    var sortable = {
        init : function() {        
            var _this = this;        
            // quit if this function has already been called
            if (arguments.callee.done){
                return;
            } else {        
                arguments.callee.done = true;
            }
            _this.DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/;
            Array.toArray(document.getElementsByTagName('table')).each(function(table, index){
                if ($(table).hasClass('sortable')) {                   
                    _this.makeSortable(table);
                }
            });        
        },
        makeSortable : function(table) {        
            var _this = this;        
            if( !table.getElementsByTagName('thead').length ) {
                // table doesn't have a tHead. Since it should have, create one and put the first table row in it.
                var thead = document.createElement('thead');
                thead.appendChild( table.rows[0] );
                table.insertBefore( thead, table.firstChild );
            }        
            // Safari doesn't support table.tHead, sigh
            if (!table.tHead){
                table.tHead = table.getElementsByTagName('thead')[0];
            }
            if (table.tHead.rows.length != 1){
                //can't cope with two header rows
                return;        
            }
            //统计行不参与排序.
            var statRows = [];
            for (var i = 0, rowCount = table.rows.length; i < rowCount; i++) {
                if ( $(table.rows[i]).hasClass('statRow') ) {
                    statRows.push( table.rows[i] );
                }
            }
            if (statRows.length) {
                if (table.tFoot == null) {
                    // table doesn't have a tfoot. Create one.
                    var tfoot = document.createElement('tfoot');
                    table.appendChild(tfoot);
                }
                for (var i = 0, statRowLength = statRows.length; i < statRowLength; i++) {
                    tfoot.appendChild(statRows[i]);
                }            
            }        
            //work through each column and calculate its type
            var headerColumns = table.tHead.rows[0].cells;        
            for (var i = 0, columnCount = headerColumns.length; i < columnCount; i++) {
                
                // manually override the type with a sortable_type attribute
                if (!$(headerColumns[i]).hasClass('nosort')) {
                    
                    var match = $(headerColumns[i]).hasFuzzyClass(/sortable_([a-z0-9]+)/, true);                
                    var override = match ? match[1] : undefined;
                    
                    headerColumns[i].sortable_sortfunction = ( override && typeof _this["sort_" + override] == 'function' ) ?  _this["sort_" + override] : _this.guessColumnSortFn(table, i);
                                    
                    // make it clickable to sort
                    headerColumns[i].sortable_columnindex = i;
                    
                    //header link can not skip.
                    $(headerColumns[i]).delegate('a', 'click', function(e){                    
                        e.preventDefault();
                    });
                    
                    $.addEvent(headerColumns[i], "click", function(e) {
                        
                        //tbody可能是后来生成的，所以不能缓存.
                        this.sortable_tbody = table.tBodies[0];
                        
                        if ($(this).hasClass('sortable_sorted')) {
                            // if we're already sorted by this column, just reverse the table, which is quicker
                            _this.reverse(this.sortable_tbody);
                            this.className = this.className.replace('sortable_sorted', 'sortable_sorted_reverse');
                            this.removeChild(document.getElementById('sortable_sortfwdind'));
                            sortrevind = document.createElement('span');
                            sortrevind.id = "sortable_sortrevind";
                            sortrevind.innerHTML = '&nbsp;&#x25BE;';
                            this.appendChild(sortrevind);
                            return;
                        }
                        if ($(this).hasClass('sortable_sorted_reverse')) {
                            // if we're already sorted by this column in reverse, just re-reverse the table, which is quicker
                            _this.reverse(this.sortable_tbody);
                            this.className = this.className.replace('sortable_sorted_reverse', 'sortable_sorted');
                            this.removeChild(document.getElementById('sortable_sortrevind'));
                            sortfwdind = document.createElement('span');
                            sortfwdind.id = "sortable_sortfwdind";
                            sortfwdind.innerHTML = '&nbsp;&#x25B4;';
                            this.appendChild(sortfwdind);
                            return;
                        }
    
                        // remove sortable_sorted classes
                        var theadrow = this.parentNode;
                        Array.toArray(theadrow.childNodes).each(function(cell) {
                            if (cell.nodeType == 1) {   //an element
                                $(cell).removeClass('sortable_sorted_reverse').removeClass('sortable_sorted');                            
                            }
                        });
                        sortfwdind = document.getElementById('sortable_sortfwdind');
                        if (sortfwdind) {
                            sortfwdind.parentNode.removeChild(sortfwdind);
                        }
                        sortrevind = document.getElementById('sortable_sortrevind');
                        if (sortrevind) {
                            sortrevind.parentNode.removeChild(sortrevind);
                        }
    
                        this.className += ' sortable_sorted';
                        sortfwdind = document.createElement('span');
                        sortfwdind.id = "sortable_sortfwdind";
                        sortfwdind.innerHTML = '&nbsp;&#x25B4;';
                        this.appendChild(sortfwdind);
    
                        // build an array to sort. This is a Schwartzian transform thing,
                        // i.e., we "decorate" each row with the actual sort key,
                        // sort based on the sort keys, and then put the rows back in order
                        // which is a lot faster because you only do getInnerText once per row
                        row_array = [];
                        col = this.sortable_columnindex;
                        rows = this.sortable_tbody.rows;
                        for (var j = 0, rowCount = rows.length; j < rowCount; j++) {
                            row_array.push( [ _this.getInnerText( rows[j].cells[col] ), rows[j] ] );
                        }
                        /* If you want a stable sort, uncomment the following line */
                        //sortable.shaker_sort(row_array, this.sortable_sortfunction);
                        /* and comment out this one */
                        row_array.sort( this.sortable_sortfunction );
                        var tb = this.sortable_tbody;
                        for (var j = 0, rowCount = row_array.length; j < rowCount; j++) {
                            tb.appendChild( row_array[j][1] );
                        }
                        delete row_array;
                    });
                }
            }
        },
        guessColumnSortFn : function(table, columnIndex) {
            // guess the type of a columnIndex based on its first non-blank row
            var sortFn = this.sort_alpha;        
            if(table.tBodies[0]){
                for (var i = 0, rowCount = table.tBodies[0].rows.length; i < rowCount; i++) {
                    var text = this.getInnerText( table.tBodies[0].rows[i].cells[columnIndex] );
                    if (text) {
                        if (text.isNumber()) {
                            return this.sort_numeric;
                        }
                        // check for a date: dd/mm/yyyy or dd/mm/yy
                        // can have / or . or - as separator
                        // can be mm/dd as well
                        var possdate = text.match(sortable.DATE_RE);
                        if (possdate) {                        
                        }
                    }
                }
            }
            return sortFn;
        },
        getInnerText : function(node) {
            var hasInputs = node.getElementsByTagName('input').length;
            if (node.getAttribute("sortable_customkey") != null) {
                return node.getAttribute("sortable_customkey");
            } else if ( typeof node.textContent != 'undefined' && !hasInputs) {
                return node.textContent.trim();
            } else if ( typeof node.innerText != 'undefined' && !hasInputs) {
                return node.innerText.trim();
            } else if ( typeof node.text != 'undefined' && !hasInputs) {
                return node.text.trim();
            } else {
                switch (node.nodeType) {
                    case 3:
                        if (node.nodeName.toLowerCase() == 'input') {
                            return node.value.trim();
                        }
                    case 4:
                        return node.nodeValue.trim();
                        break;
                    case 1:
                    case 11:
                        var innerText = '';
                        for (var i = 0; i < node.childNodes.length; i++) {
                            innerText += this.getInnerText( node.childNodes[i] );
                        }
                        return innerText.trim();
                        break;
                    default:
                        return '';
                }
            }
        },
        //reverse the rows in a tbody
        reverse : function(tbody) {        
            var newrows = [];
            for (var i = 0, len = tbody.rows.length; i < len; i++) {
                newrows.push( tbody.rows[i] );
            }
            for (var i = newrows.length - 1; i >= 0; i--) {
                tbody.appendChild( newrows[i] );
            }
            delete newrows;
        },
        /*
         * sort functions
         * each sort function takes two parameters, a and b
         * you are comparing a[0] and b[0]
         */
        sort_numeric : function(a, b) {
            var aa = parseFloat(a[0].replace(/[^0-9.-]/g, '')) || 0;        
            var bb = parseFloat(b[0].replace(/[^0-9.-]/g, '')) || 0;        
            return aa - bb;
        },
        sort_alpha : function(a, b) {
            if (a[0] == b[0])
                return 0;
            if (a[0] < b[0])
                return -1;
            return 1;
        },    
        sort_date : function(a, b) {        
            var dateA = Date.from(a[0]),
                dateB = Date.from(b[0]);
            return dateA - dateB;
        },
        shaker_sort : function(list, comp_func) {
            // A stable sort function to allow multi-level sorting of data
            // see: http://en.wikipedia.org/wiki/Cocktail_sort
            // thanks to Joseph Nahmias
            var b = 0;
            var t = list.length - 1;
            var swap = true;
            while (swap) {
                swap = false;
                for (var i = b; i < t; ++i) {
                    if (comp_func(list[i], list[i + 1]) > 0) {
                        var q = list[i];
                        list[i] = list[i + 1];
                        list[i + 1] = q;
                        swap = true;
                    }
                }
                t--;
                if (!swap)
                    break;
                    
                for (var i = t; i > b; --i) {
                    if (comp_func(list[i], list[i - 1]) < 0) {
                        var q = list[i];
                        list[i] = list[i - 1];
                        list[i - 1] = q;
                        swap = true;
                    }
                }
                b++;
            }
        }
    };
    
    $(window).load(function(){
        sortable.init();    
    });    
    
    //------------------------------------------------------------------------------------------------------------------    
    var _defaultConfig = {
        key : 'id',
        isInitSelectFirst : true    //是否初始化选中第一行.
    };
    
    function _Table($table, config){
        Observable.enable(this);
        var _this = this;
        this.config = $.extend(true, {}, _defaultConfig, config); 
        this.$wrapper = $($table);
        this.$table = this.$wrapper.closest('table');
        this.$thead = this.$table.find('thead');
        this.$tbody = this.$table.find('tbody');
        this.$parent = this.$table.parent(); 
        this.data = {};        
        this.$table.addClass('ujsTable');
        this.$table.style(this.config['style']);
        if(this.config['selectable'] !== false){
            this.enableSelectable();
        }
        if(this.config['isFreezingHeader'] !== false){
            this.enableFreezingHeader();
        }
        if(this.config['rowExpanderTmp']){
            this.config['isEnableRowExpander'] = true;
            this.enableRowExpander();
        }
        if(this.config['isFreezingHeader'] !== false){
            (function(){
                //TODO:自动计算动态列的宽度. 当页面大小调整时如何办?
                var $autoWidthColumn = _this.$thead.find('.autoWidth');
                if($autoWidthColumn.isExist()){
                    $('body').prepend('<style type="text/css">.autoWidth{width:{0}px;}</style>'.format(
                        _this.$table.width() - 
                        $autoWidthColumn.siblings().map(function(index, th){return $(th).width();}).get().sum() 
                    ));
                }          
            }).defer();
        }
        
        /**
         * 触发初始化完成事件。使用异步的方式，是为了使在创建Table对象之后注册的init事件能够生效。
         * 
         * 如下:
         * page.productListObj = $productList.table({
		 *		key : 'id',
		 *		style : 'height:450px',
		 *		rowTmp : productItemTmp,
		 *		rowExpanderTmp : '<div><img src="<%=o.productpic%>" /></div>'
		 * }).data('table');
		 * 
		 * page.productListObj.on('init', function(){
		 *		this.$wrapper.delegate('.editProduct', 'click', function(){
		 *			var rowID = $(this).attr('rowid');
		 *			alert(rowID);
		 *		});
		 * }); 
         */  
        (function(){
        	_this.fire('init');
        }).defer();    
    }
    
    /**
     * 加载表格数据.
     * @param {Object} rows
     *
     * @return {this}
     */
    _Table.prototype.load = function(rows, append) {
        //thead.
        if (this.config['headTmp'] && !this.$thead.length) {
            this.$thead = $('<thead></thead>').appendTo(this.$table);
            this.$thead.html(this.config['headTmp']);
            lan.parse(this.$thead);
        }
        //tbody        
        if(!this.$tbody.length){
            this.$tbody = $('<tbody></tbody>').appendTo(this.$table);
            this._initTbodyHeight();
        }
        if (!append) {
            this.$tbody.html('');
        }        
        //若rows为Object时, 将Object中的key-value以表格显示。        
        if(Type.isObject(rows)){
            var newRows = [];
            for(var key in rows){
                newRows.push({
                    key : key,
                    value : rows[key]
                });
            }
            rows = newRows;
        }
        for (var i = 0, len = rows.length; i < len; i++) {
            var rowData = rows[i];
            this.data[rowData[this.config['key']]] = rowData;
            rowData['rowType'] = (i % 2 === 0) ? '' : 'odd';
            rowData['index'] = i + 1;
            this.$tbody.append(tmpl(this.config['rowTmp'], rowData));
        }        
        //默认选中第一行.
        if(this.config['isInitSelectFirst'] === true){
            this.$tbody.find('tr:eq(0)').trigger('click');
        }
        if(this.config['isEnableRowExpander']){            
            //tbody修复. 
            this._repairRowExpanderColumn();            
        }
        return this;
    };
    
    //--------------------------------------------------------------------------------
    
    /**
     * 删除行
     */
    _Table.prototype.deleteRow = function(rowID){
    	var $row = this.getRowByID(rowID);
    	$row.remove();
    	delete this.data[rowID];
    	return this;
    };
    
    //--------------------------------------------------------------------------------
    
    /**
     * data为全量data.
     */
    _Table.prototype.updateRow = function($tr, data){
        var $newTr = $(tmpl(this.config['rowTmp'], data));
        if(this.config['rowExpanderTmp']){
        	this._repairRowExpanderColumn($newTr);
	        if($tr.find('.rowExpanderTrigger').attr('loadtype') == 'loaded'){
	        	$tr.next().remove();
	        }	
        }
        $tr.replaceWith($newTr);
        return $newTr;    
    };
    
    /**
     * data为增量data.
     */
    _Table.prototype.updateRowByID = function(id, data){
    	var oldData = page.productListObj.getRowData(id),
        	newData = $.extend(true, oldData, data);
		return this.updateRow(this.getRowByID(id), newData);    
    };
    
    //--------------------------------------------------------------------------------
    
    _Table.prototype.getData = function(){
        return this.data;
    };
            
    _Table.prototype.getRowData = function(rowID){
        return this.data[rowID];
    };
    
    //--------------------------------------------------------------------------------
    
    _Table.prototype.setData = function(tableData){
        this.data = tableData || {};
        return this;
    };
    
    _Table.prototype.setRowData = function(rowID, rowData){
        this.data[rowID] = rowData;
        return this;
    };
            
    //--------------------------------------------------------------------------------
    
    _Table.prototype.getRowByID = function(id){
        return this.$table.find('tbody tr[{0}=\'{1}\']'.format(this.config['key'], id));
    };
    
    _Table.prototype.getSelectedRowIDs = function(){ 
        var _this = this;
        return this.getSelectedRows().map(function() {
            return $(this).attr(_this.config['key']);
        }).get();        
    };
    
    _Table.prototype.getSelectedRowID = function(){        
        return this.getSelectedRow().attr(this.config['key']);        
    };
    
    _Table.prototype.getSelectedRow = function(){
        return this.getSelectedRows().first();    
    };
    
    _Table.prototype.getSelectedRows = function(){
        return this.$table.find('tr.selected');
    };
    
    _Table.prototype.getAllRows = function(){
        return this.$table.find('tbody tr');
    };
    
    _Table.prototype.getAllRowIDs = function(){
        var _this = this;
        var $rows = this.getAllRows();
        return $rows.map(function() {
            return $(this).attr(_this.config['key']);
        }).get();  
    };
     
    _Table.prototype.getSelectedRowsCount = function(){
        return this.getSelectedRows().length;
    };
    
    _Table.prototype.getSelectedRowData = function(){
        var rowsData = this.getSelectedRowsData();        
        return rowsData.length ? rowsData[0] : null;
    };
    
    _Table.prototype.getSelectedRowsData = function(){
        var _this = this;        
        return this.getSelectedRows().map(function(){
            return _this.data[$(this).attr(_this.config['key'])];
        }).get();
    };
    
    //--------------------------------------------------------------------------------
     
    _Table.prototype.showMask = function(msg){
        ujs.ui.paneMask.show(this.$parent, msg || 'loading');
        return this;
    };
    _Table.prototype.hideMask = function(){        
        ujs.ui.paneMask.hide(this.$parent);
        return this;
    };
    
    //-------------------------------------------------------------------------------- 
    
    _Table.prototype.enableSelectable = function(){
        var _this = this;    
        this.$table.addClass('selectable');               
        //select all | unselect all
        this.$table.delegate('#chkAll', 'change', function(){
            var isChecked = $(this).is(':checked');         
            $('td>input[type=checkbox]', _this.$table).attr('checked', isChecked);
            $('tr', _this.$table)[ isChecked ? 'addClass' : 'removeClass']('selected'); 
            _this.$table.attr('isAllSelected', isChecked);  
            _this.fire('checkAllChange', isChecked);
        });        
        //ctrl+A full selection.
        $(window).keydown(function(e) {         
            if ((e.which == 97 || e.which == 65) && event.ctrlKey){             
                var checkAll = $('#chkAll', _this.$table),
                    isChecked = !checkAll.is(':checked');
                checkAll.attr('checked', isChecked);
                checkAll.trigger('change');
                _this.$table.attr('isAllSelected', isChecked);
                _this.fire('checkAllChange', isChecked);
            }
        });        
        //checkbox click.
        this.$table.delegate('td input[type=checkbox]','click',function(e){
            e.stopPropagation();
            var isChecked = $(this).is(':checked'),
                $row = $(this).closest('tr');  
                                            
            if(!isChecked){
                $row.removeClass('selected');               
                $('#chkAll').attr('checked', false);
            } else {
                $row.addClass('selected');
            }           
            _this.fire('itemCheckChange', $row, isChecked);
        });
        
        //click selection.
        this.$table.delegate('tbody>tr','click',function(e){           
            var $row = $(this);
            var otherRows = $row.siblings();
            var currentChk = $row.find('input[type=checkbox]');         
            if(e.ctrlKey){
                //if ctrl key is pressed, toggle selected.
                $row.toggleClass('selected');
                currentChk.attr('checked', $row.hasClass('selected'));  
            } else {
                //common click, just selected.
                $row.addClass('selected');
                currentChk.attr('checked', true);
            }
            if(!e.ctrlKey){
                otherRows.removeClass('selected');
                otherRows.find('input[type=checkbox]').attr('checked', false);  
            }
            _this.$table.attr('isAllSelected', false);          
            _this.fire('itemClick', $row);
        });
            
        //dblclick  
        this.$table.delegate('tbody>tr','dblclick',function(e){           
            _this.fire('itemDblClick', $(this));
        });
        
        //right click if no multiple selected.
        this.$table.delegate('tbody>tr','mousedown',function(e){
            var $row = $(this);
            if( e.button == 2 && $(this).parent().find('.selected').length < 2) {
                $(this).trigger('click');
                _this.$table.attr('isAllSelected', false);            
                _this.fire('itemClick', $row);              
            }
        }); 
        return this;
    };
    
    //--------------------------------------------------------------------------------
    
    _Table.prototype.enableRowExpander = function(){
        var _this = this;
        //thead修复.
        var hasRowExpanderColumn = !!this.$table.find('thead tr .rowExpanderColumn').length;        
        if(!hasRowExpanderColumn){               
            this.$table.find('thead tr').prepend('<th class="rowExpanderColumn"></th>');
        }
        if(!hasRowExpanderColumn){
            this.$table.delegate('.rowExpanderTrigger', 'click', function(e){            
                var $rowExpanderTrigger = $(this),
                    isLoaded = $rowExpanderTrigger.attr('loadType'),
                    $tr = $rowExpanderTrigger.parents('tr'),
                    rowID = $tr.attr(_this.config['key']),
                    rowInfo = _this.getRowData(rowID);                                    
                $rowExpanderTrigger.toggleClass('expanded');
                if(!isLoaded){                	
                    var $rowExpanderBody = $(
                        [
                            '<tr {key}="{id}" class="expanderRow">',
                                '<td class="rowExpanderBodyCtn" colspan="{colsCount}">',
                                    '{rowExpanderBody}',
                                '</td>',
                            '</tr>'
                        ].join('').format({
                            key : _this.config['key'],
                            id : rowID,
                            colsCount : _this.$table.find('thead tr th').length, 
                            rowExpanderBody : tmpl(_this.config['rowExpanderTmp'], rowInfo)
                        })
                    );                    
                    $tr.after($rowExpanderBody);
                    $rowExpanderTrigger.attr('loadType', 'loaded');
                } else {
                    $tr.next().toggle();    
                }       
            }); 
            //双击row时，打开row-expand.
            this.$table.delegate('tr', 'dblclick', function(){
                $(this).find('.rowExpanderTrigger').trigger('click');
            });
            //关闭row-expand.
            this.$table.delegate('.rowExpanderBodyCtn button.close', 'click', function(e){   
                $(this).closest('tr').prev().find('.rowExpanderTrigger').trigger('click');                  
            });
        };
        return this;
    };
    
    _Table.prototype._repairRowExpanderColumn = function($trs){
    	($trs || this.$table.find('tbody tr:not(.expandable)')).prepend([
            '<td class="rowExpanderColumn">',
                '<span class="rowExpanderTrigger"></span>',
            '</td>'
        ].join('')).addClass('expandable');
        return this;
    };
    
    //--------------------------------------------------------------------------------
    
    _Table.prototype._initTbodyHeight = function(){
        var _this = this;
        //$tbody CSS属性
        (function(){
            _this.$tbody.css({     
                height: 'calc(100% - {0}px)'.format(_this.$thead.outerHeight())       
            });    
        }).defer(0);
        return this;
    };
    
    _Table.prototype.enableFreezingHeader = function(){
        var _this = this;
        this.$table.addClass('freezingHeader');
        if(!this.$table.isPositioned()){
            this.$table.css({
                'position' : 'relative'
            });    
        }
        
        //$thead为绝对定位
        this.$thead.css({});            
        
        //$thead与$tbody的共有CSS属性           
        this.$thead.add(this.$tbody).css({});
        
        this._initTbodyHeight();
        
        //水平滚动连动.
        this.$tbody.syncScrollWith(this.$thead, function($firstElement, $secondElement, scrollLeft, scrollTop){
            $secondElement.css('left', -scrollLeft);
        });
        return this;
    };
    
    //--------------------------------------------------------------------------------
     
    //init interface.
    $.fn.table = function(config){        
        this.each(function(){
            var tableObj = new _Table(this, config);                               
            $(this).data('table', tableObj);
        });
        return this;
    };    
})(jQuery);